package com.zisu.wcsopcua.opcua.controller;

import com.zisu.wcsopcua.common.utils.CommonFunction;
import com.zisu.wcsopcua.common.websocket.WebSocketServer;
import com.zisu.wcsopcua.opcua.Consumer.EventReceivedCallBack;
import com.zisu.wcsopcua.opcua.arg.*;
import com.zisu.wcsopcua.opcua.config.LocalMulPLCConfig;
import com.zisu.wcsopcua.opcua.constant.PLCConstant;
import com.zisu.wcsopcua.opcua.selfunion.Enum.PLCType;
import com.zisu.wcsopcua.opcua.selfunion.NodeIdKey;
import com.zisu.wcsopcua.opcua.selfunion.UAService;
import com.zisu.wcsopcua.opcua.selfunion.entity.*;
import org.eclipse.milo.opcua.sdk.client.api.subscriptions.UaMonitoredItem;
import org.eclipse.milo.opcua.stack.core.types.builtin.DataValue;
import org.eclipse.milo.opcua.stack.core.types.builtin.NodeId;
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UInteger;
import org.eclipse.milo.opcua.stack.core.types.builtin.unsigned.UShort;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;

@RestController
@RequestMapping("/opcua")
public class OperateController {
    private static final Logger logger = LoggerFactory.getLogger(OperateController.class);

    //todo controller 需要仔细编写，，重点关注 那些报错异常（断网、opc-server宕机）
    @Autowired
    UAService uaService;

    @Autowired
    WebSocketServer webSocketServer;

    @PostMapping("/addThisPlc")
    /**
     *  @Description
     *  @Param [
     *              plcName,        plc3
     *              urlConfig,      opc.tcp://WIN-92SDA5G5VE8:55101   //换成ip就行，如果这样要配置域名解析
     *              policyConfig,   None    //None,Basic256,Basic128Rsa15
     *              userConfigs,    ""      //如果无账号密码填空,如果有 账号密码逗号隔开 如  CXCX,123456789
     *              ip,             WIN-92SDA5G5VE8
     *              messageMode     1       //None(1),Sign(2),SignAndEncrypt(3);
     *        ]
     *  @Return com.qgs.dc.opcua.opcuamilnow.controller.R
     *  @Author caixiang
     *  @Date 2020/7/28  11:15
     **/
    public R addThisPlc(@RequestBody AddPLCArgEntity addPLCArgEntity){
        try {
            Integer integer = uaService.dynamicAddPlc(addPLCArgEntity.getPlcName(), addPLCArgEntity.getUrlConfig(), addPLCArgEntity.getPolicyConfig(),addPLCArgEntity.getUserConfigs() , addPLCArgEntity.getIp(),addPLCArgEntity.getMessageMode());
            return R.ok().put("result",integer);
        }catch (Exception e){
            String s = uaService.extractError(e.getMessage());
            s = s+"；详细："+e.getMessage();
            return R.error().put("result",s);
        }
    }

    public void unlinkSubscribeWhenRemovePlc(String plcName) throws Exception {
        //并且把 这个plc 已订阅的 变量  给删除          -- 开始
        List<CurrentSubEntity> currentSubscribeVarForVisited = uaService.getCurrentSubscribeVarForVisited(plcName);
        List<Integer> ns = new ArrayList<>();
        List<String> iden = new ArrayList<>();
        if(currentSubscribeVarForVisited == null){
            return;
        }
        for(CurrentSubEntity currentSubEntity:currentSubscribeVarForVisited){
            ns.add(currentSubEntity.getNameSpace());
            iden.add(currentSubEntity.getIdentifier());
        }
        uaService.deSubscribeForVisit(ns, iden, plcName);
        //并且把 这个plc 已订阅的 变量  给删除          -- 结束
    }

    @DeleteMapping("/removeThisPlc")
    public R removeThisPlc(@RequestBody DelPlcArgEntity delPlcArgEntity){
        //return R.ok().put("status",uaService.dynamicRemovePlc(plcName));
        try {
            Integer integer = uaService.dynamicRemovePlc(delPlcArgEntity.getPlcName());

            unlinkSubscribeWhenRemovePlc(delPlcArgEntity.getPlcName());
            if(integer == null){
                return R.error().put("result",delPlcArgEntity.getPlcName()+" 这台plc不存在 / 或参数异常");
            }
            return R.ok().put("result",integer);
        }catch (Exception e){
            String s = uaService.extractError(e.getMessage());
            return R.error().put("result",s);
        }
    }

    @PostMapping("/getBrows")
    public R getBrows(@RequestBody BrowsArgEntity browsArgEntity){
        NodeIdKey nodeIdKey = uaService.browseA(browsArgEntity.getPlcName(), browsArgEntity.getRootNameSpace(), browsArgEntity.getIdenrifier());
        if(nodeIdKey == null){
            return R.error().put("result",browsArgEntity.getPlcName()+" 这台plc不存在 / 或参数异常");
        }
        return R.ok().put("result",nodeIdKey);
    }

    //如果是数组类型 的那么 newValue 就是 一个字符串 并且用 逗号隔开，后面跟着 数据类型 如  "1,2,4|QUByte"
    /**
     * 参数
     * {
     *     "nameSpace": 6,
     *     "identifier": "S7-1200 station_1.PLC_1.TestDB80.Array[0..7] of DInt1",
     *     "newValue": "1,2,3,4,5,6,7,8#QInteger",
     *     "type": "QArray",
     *     "plcName": "plc1"
     * }
     *
     * */
    @PostMapping("/write")
    //public R write(Integer nameSpace,String identifier,String newValue,String type,String plcName){
    public R write(@RequestBody WriteArgEntity writeArgEntity){
        try {
            //PLCType.valueOf(type).convertType(newValue); 意思是 把newValue 这个数据转成type 类型的变量
            Object var = PLCType.valueOf(writeArgEntity.getType()).convertType(writeArgEntity.getNewValue());
            Boolean aBoolean = uaService.setValue(writeArgEntity.getNameSpace(), writeArgEntity.getIdentifier(), var, writeArgEntity.getPlcName());
            if(aBoolean == null){
                return R.error().put("result",writeArgEntity.getPlcName()+" 这台plc不存在 / 或参数异常");
            }else if(aBoolean){
                return R.ok();
            }else {
                return R.error();
            }
        }catch (Exception e){
            return R.error().put("result", e.getMessage());
        }

    }

    @PostMapping("/read")
    //public R read(Integer nameSpace,String identifier,String plcName) {
    public R read(@RequestBody ReadArgEntity readArgEntity) {
        try {
            DataValue dv = uaService.getValue(readArgEntity.getNameSpace(), readArgEntity.getIdentifier(), readArgEntity.getPlcName());
            if(dv == null){
                return R.error().put("result",readArgEntity.getPlcName()+" 这台plc不存在 / 或参数异常");
            }
            Object value = dv.getValue().getValue();
            logger.info(CommonFunction.judgeVarType(value));
            return R.ok().put("result", CommonFunction.var(value));
        }catch (Exception e){
            return R.error().put("result", e.getMessage());

        }
    }

//    @PostMapping("/fortest")
//    //public R read(Integer nameSpace,String identifier,String plcName) {
//    public R fortest(@RequestBody ReadArgEntity readArgEntity) {
//        try {
//            Integer integer = waitingForTarget(Plc2WcsVarEnum.TEST, 2, 3000L);
//            return R.ok().put("result", integer);
//        }catch (Exception e){
//            return R.error().put("result", e.getMessage());
//
//        }
//    }
//    //-1 超时退出； 2 opc 访问异常；1 成功
//    private Integer waitingForTarget(Plc2WcsVarEnum var, Integer targetValue, Long deadLine) {
//        Integer res = 0;
//        long start = System.currentTimeMillis();
//        while (true){
//            try {
//                long end = System.currentTimeMillis();
//                if((end-start)>deadLine){
//                    res = -1;
//                    break;
//                }
//
//                String value = uaService.getValue(var.getNamespace(), var.getIdentity(), "plc1").getValue().getValue().toString();
//                Integer now = Integer.valueOf(value);
//
//                if(targetValue == now){
//                    res = 1;
//                    break;
//                }
//            }catch (Exception e){
//                logger.error("opc 访问异常。");
//                res = 2;
//                break;
//            }
//        }
//        return res;
//    }

//    @PostMapping("/readwriteForTest")
//    //public R read(Integer nameSpace,String identifier,String plcName) {
//    public R readwriteForTest() {
//        for(int i = 0;i<50;i++){
//            MultiWorkRunnable<Long> multiWorkRunnable1 = new MultiWorkRunnable<>(uaService);
//            Future<Long> submit1 = QGSThreadPool.executor.submit(multiWorkRunnable1);
//        }
//        return R.ok();
//    }


    //获取这台plc 所有订阅的变量
    @PostMapping("getAllSubscribeVarOfPlcNameWithLocal")
    public R getAllSubscribeVarOfPlcNameWithLocal(@RequestBody SubscribeVarArgEntity subscribeVarArgEntity) throws Exception {
        if(subscribeVarArgEntity.getPlcName()==null){
            return R.error("plcName 不能为空");
        }
        ArrayList<SubscribeVarOfConfigEntity> subscribedVarOfConfig = LocalMulPLCConfig.getSubscribedVarOfConfig(subscribeVarArgEntity.getPlcName());
        for(SubscribeVarOfConfigEntity entity: subscribedVarOfConfig){
            Object value = uaService.getValue(entity.getNameSpace(), entity.getIdentifier(), entity.getPlcName()).getValue().getValue();
            entity.setNowValue(value.toString());
        }
        Map<Integer, List<SubscribeVarOfConfigEntity>> map = subscribedVarOfConfig.stream().collect(Collectors.groupingBy(t -> t.getPeriod()));

        return R.ok().put("result",map);
    }

    @PostMapping("saveAllSubscribeVarOfPlcNameToLocal")
    public R saveAllSubscribeVarOfPlcNameToLocal(@RequestBody List<SubscribeVarOfConfigEntity> subscribeVars) throws Exception {

        LocalMulPLCConfig.writeIntoFileOfSubscribeVar(subscribeVars);
        return R.ok().put("result","成功");
    }

    //todo 1.订阅变量数组...（并且把数据类型返还给前端）
    /**
     * 含义 : 可以同时订阅：变量、数组 ( 注意这个是功能型订阅 )
     * 参数 : SubscribeArgEntity
     *          listNameSpace   List<Integer>
     *          listIdentifier  List<String>
     *          plcName         String
     *          period          Integer
     * 注意 : ns 和 identify 必须位置上相互对应，，判断是否数组依据就是看几组数据
     * 返回：
     *      这边是通过websocket 返回的 是一个字符串
     *      第一组 是变量名
     *      第二组 是变量值
     *      第三组 是变量值 的类型（如果是数组 那就是QArray）
     *      第四组 是变量值 的类型（如果是数组 那这组就存在，代表数组里面变量的数据类型）
     *      3,Byte|8,7,7,7,8|QArray|QShort|status|time
     *      3,Byte|0|QUByte|status|time
     * */
    @PostMapping("subscribeVarForFunction")
    public R subscribeVarForFunction(@RequestBody SubscribeVarArgEntity subscribeVarArgEntity) throws Exception {

        Integer integer = uaService.subscribeValues(subscribeVarArgEntity.getListNameSpace(), subscribeVarArgEntity.getListIdentifier(), new Double(subscribeVarArgEntity.getPeriod()==null?1000:subscribeVarArgEntity.getPeriod()),
                    (item, dataValue) -> {

                System.err.println("（测试是否 每隔intervel 都会执行这个回调方法） subscription value received: item:NodeId : " +
                        "" + item.getReadValueId().getNodeId() + ", value :" + dataValue.getValue());

                NodeId currentNode = item.getReadValueId().getNodeId();
                UShort namespaceIndex = currentNode.getNamespaceIndex();
                Object identifier = currentNode.getIdentifier();

                Object value = dataValue.getValue().getValue();
                String varType = CommonFunction.judgeVarType(value);
                String status = CommonFunction.quality(dataValue.getStatusCode());
                String time = dataValue.getSourceTime().getJavaDate().toString();
                String res = namespaceIndex + "," + identifier + "|" + CommonFunction.var2String(value) + "|" + varType+ "|" +subscribeVarArgEntity.getPlcName()+"|"+status+"|"+time;

                try {
                    webSocketServer.sendtoAll(res);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }, subscribeVarArgEntity.getPlcName(),PLCConstant.Subscription_Function_Var);

        if(integer == null){
            return R.error().put("result",subscribeVarArgEntity.getPlcName()+" 这台plc不存在 / 或参数异常");
        }
        return R.ok().put("result","ok");
    }

    /**
     * 含义 : 订阅变量，界面展示用用
     * 参数 : SubscribeArgEntity
     *          listNameSpace   List<Integer>
     *          listIdentifier  List<String>
     *          plcName         String
     *          period          Integer
     * 注意 : ns 和 identify 必须位置上相互对应，，判断是否数组依据就是看几组数据
     * 返回：
     *      1  <===> 你要订阅的这个Node 订阅成功（包括这个变量已存在 然后你再次去订阅）
     *      -1  <===> 你要订阅的这个Node 订阅失败
     *      2  <===> 你要订阅的这个Node 已订阅，请勿重复订阅
     * websocket 传递格式：
     *      这边是通过websocket 返回的 是一个字符串
     *      第一组 是变量名
     *      第二组 是变量值
     *      第三组 是变量值 的类型（如果是数组 那就是QArray）
     *      第四组 是变量值 的类型（如果是数组 那这组就存在，代表数组里面变量的数据类型）
     *      第五组 是这个变量所属 的plc，，是哪个plc
     *      3,Byte|8,7,7,7,8|QArray|QShort|plcName|status|time
     *      3,Byte|0|QUByte|plcName|status|time
     * */
    @PostMapping("subscribeVarForVisit")
    public R subscribeVarForVisit(@RequestBody SubscribeVarArgEntity subscribeVarArgEntity){

        try {
            Integer integer = uaService.subscribeForVisit(subscribeVarArgEntity.getListNameSpace(), subscribeVarArgEntity.getListIdentifier(), new Double(subscribeVarArgEntity.getPeriod()==null?1000:subscribeVarArgEntity.getPeriod()), (item, dataValue) -> {
                UInteger attributeId = item.getReadValueId().getAttributeId();

                System.err.println("（测试是否 每隔intervel 都会执行这个回调方法） subscription value received: item:NodeId : " +
                        "" + item.getReadValueId().getNodeId() + ", value :" + dataValue.getValue()+",attributeId:"+attributeId);

                NodeId currentNode = item.getReadValueId().getNodeId();
                UShort namespaceIndex = currentNode.getNamespaceIndex();
                Object identifier = currentNode.getIdentifier();
                Object value = dataValue.getValue().getValue();
                String varType = CommonFunction.judgeVarType(value);

                String status = CommonFunction.quality(dataValue.getStatusCode());
                String time = dataValue.getSourceTime().getJavaDate().toString();
                String res = namespaceIndex + "," + identifier + "|" + CommonFunction.var2String(value) + "|" + varType+ "|" +subscribeVarArgEntity.getPlcName()+"|"+status+"|"+time ;

                try {
                    webSocketServer.sendtoAll(res);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }, subscribeVarArgEntity.getPlcName());
            if(integer == null){
                return R.error().put("result",subscribeVarArgEntity.getPlcName()+" 这台plc不存在 / 或参数异常");
            }

            if(integer == 1){
                return R.ok().put("result","成功");
            }else if(integer == 2) {
                return R.error().put("result","你要订阅的这个Node 已订阅，请勿重复订阅");
            }else {
                return R.error().put("result","你要订阅的这个Node 订阅失败");
            }
        }catch (Exception e){
            return R.error().put("result",e.getMessage());
        }
    }
    /**
     * 状态： 暂时还未用到
     * 含义 : 订阅一个或者多个事件
     * 参数 : SubscribeArgEntity
     *          listNameSpace   List<Integer>
     *          listIdentifier  List<String>
     *          plcName         String
     * 注意 : ns 和 identify 必须位置上相互对应，，判断是否数组依据就是看几组数据
     * 返回：
     *      把事件触发 时候的一些数据 拿过来（通过过滤器过滤过来） 然后传给前端。
     * status: 暂停（等待事件 验证好了之后 再做这些）
     * */
    @PostMapping("subscribeEventsForFunction")
    public R subscribeEventsForFunction(@RequestBody SubscribeEventArgEntity subscribeVarArgEntity) throws Exception {
        if(subscribeVarArgEntity.getListNameSpace().size()!=subscribeVarArgEntity.getListIdentifier().size()){
            return R.error("传入参数不正确");
        }
        List<UaMonitoredItem.EventConsumer> list = new ArrayList<>();
        int size = subscribeVarArgEntity.getListNameSpace().size();
        UaMonitoredItem.EventConsumer a1 =((uaMonitoredItem, variants) -> {
            logger.info("Server Event Received from {}", uaMonitoredItem.getReadValueId().getNodeId());
            //这里variants 就是 0,2253 这个事件的 变量
            for (int i = 0; i < variants.length; i++) {
                logger.info("\t variant[{}]: {}", i, variants[i].getValue());
            }
        });
        UaMonitoredItem.EventConsumer a2 =((uaMonitoredItem, variants) -> {
            logger.info("myDevice Event Received from {}", uaMonitoredItem.getReadValueId().getNodeId());
            //这里variants 就是 0,2253 这个事件的 变量
            for (int i = 0; i < variants.length; i++) {
                logger.info("\t variant[{}]: {}", i, variants[i].getValue());
            }
        });
        UaMonitoredItem.EventConsumer a3 = new EventReceivedCallBack();

        for(int i=0;i<size;i++){
            list.add(a3);
        }
        Integer plc1 = uaService.subscribeEvents(subscribeVarArgEntity.getPlcName(), subscribeVarArgEntity.getListNameSpace(), subscribeVarArgEntity.getListIdentifier(),list);
        if(plc1 == null){
            return R.error().put("result",subscribeVarArgEntity.getPlcName()+" 这台plc不存在 / 或参数异常");
        }
        return R.ok().put("result",plc1);
    }

    /* *
     * 原因：订阅包含一个寿命计数器，保存了在没有发布请求时经历的循环次数，当达到阈值时，会删除这个订阅以及与订阅相关的监控项。在删除订阅时，会发送一条StateChangeNotification消息，并携带状态码Bad_Timeout
     * */


    /**
     * 含义：删除 某个订阅分类下 的 某个Node 或者 List<Node>(包括事件 和变量 )
     * 参数：
     *      1.plcName
     *      2.你要取消订阅 事件的 nameSpace 和 identifier
     *      3.可以选择你要过滤的条件（现在暂时无，，后续可以补充）
     *      4.设置回调函数（就是当有订阅 的事件发生的时候 就调用的函数 ）
     *      5.注意type 是 你要删除的订阅是在哪个（1 == 代表 展示型订阅；；2 == 代表 功能型(变量) 订阅；；3 == 代表 功能型(事件) 订阅）
     * 返回：Integer
     *      1  <===> 代表 删除订阅的事件 成功
     *      -2 <===> 代表 你要删除的订阅事件 不存在
     *      -1 <===> 代表 删除订阅失败/参数错误
     *      如果有异常就直接抛出异常
     *      null      代表选中的plc异常
     *
     *  注意：如果之前订阅是 批量订阅的额（也就是个List），在delSubscribe的时候可以分开取消订阅。
     *
     * */
    @PostMapping("delSubscribe")
    public R delSubscribe(@RequestBody DelSubscribeEntity delSubscribeEntity) throws Exception {
        Integer integer = uaService.deleteSubscribe(delSubscribeEntity.getPlcName(), delSubscribeEntity.getListNameSpace(), delSubscribeEntity.getListIdentifier(),delSubscribeEntity.getType());
        if(integer == null){
            return R.error().put("result",delSubscribeEntity.getPlcName()+" 这台plc不存在 / 或参数异常");
        }
        return R.ok().put("result",integer);
    }

    /**
     * 含义：删除某个已订阅的变量
     * 注意：
     *      1.
     * 参数：
     *          回调函数BiConsumer<UaMonitoredItem, DataValue>，是当你订阅的这个变量当变量发生 改变的时候 你执行的方法(刚开始 会执行一次)
     * 返回值：
     *                1  <===> 你要 取消订阅的这个Node 成功（包括这个变量已存在 然后你再次去订阅）
     *                -1  <===> 你要 取消订阅的这个Node 失败
     *                2 <===> 你要 取消订阅的这个Node 不存在
     *                有异常直接抛出
     *                null      代表选中的plc不存在
     *
     *      tip：Subscription有两种模式，一种是Reporting，另一种是Sampling。
     *      如果定义为Sampling，则这个Subscription是一个Triggered Item，即被激发的订阅，需要一个定义为Reporting的Subscription（称为Triggering Item）
     * 与它连接。这样当Triggering Item更新时，会激发Triggered Item更新。
     *
     * */
    @PostMapping("deSubscribeVarForVisit")
    public R deSubscribeVarForVisit(@RequestBody SubscribeVarArgEntity subscribeVarArgEntity) {

        try {
            Integer integer = uaService.deSubscribeForVisit(subscribeVarArgEntity.getListNameSpace() , subscribeVarArgEntity.getListIdentifier() , subscribeVarArgEntity.getPlcName());
            if(integer == null){
                return R.error().put("result",subscribeVarArgEntity.getPlcName()+" 这台plc不存在 / 或参数异常");
            }
            if(integer == 1){
                logger.info("取消订阅变量:"+subscribeVarArgEntity.toString() +"成功");
                return R.ok().put("result","成功");
            }else if(integer == 2) {
                logger.error("取消订阅变量:"+subscribeVarArgEntity.toString() +"失败，取消订阅的这个Node 不存在");
                return R.error().put("result","取消订阅的这个Node 不存在");
            }else {
                logger.error("取消订阅变量:"+subscribeVarArgEntity.toString() +"失败");
                return R.error().put("result","取消订阅的这个Node 失败");
            }
        }catch (Exception e){
            return R.error().put("result",e.getMessage());
        }

    }

    @PostMapping("getCurrentSubscribeVarForVisited")
    public R getCurrentSubscribeVarForVisited(@RequestBody GetCurrentSubArgEntity currentSubArgEntity) {
        try {
            List<CurrentSubEntity> currentSubscribeVarForVisited = uaService.getCurrentSubscribeVarForVisited(currentSubArgEntity.getPlcName());

            if(currentSubscribeVarForVisited == null){
                return R.error().put("result",currentSubArgEntity.getPlcName()+"这台plc不存在 或是 订阅不存在");
            }
            return R.ok().put("result",currentSubscribeVarForVisited);
        }catch (Exception e){
            return R.ok().put("result",e.getMessage());
        }
    }

    @PostMapping("getUniqueWebSocketId")
    public R getUniqueWebSocketId() throws Exception {
        String uniqeId = webSocketServer.getUniqeId();
        return R.ok().put("result",uniqeId);
    }


    @PostMapping("getConnectedPLC")
    public R getConnectedPLC() {
        return R.ok().put("result",uaService.getConnectedPLC());
    }

}
